package com.gxa.jd.service.impl;


import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.gxa.exception.CustomerException;
import com.gxa.exception.MessageCode;
import com.gxa.jd.config.AlipayConfig;
import com.gxa.jd.config.WxPayConfig;
import com.gxa.jd.enums.PayConstant;
import com.gxa.jd.enums.PayStatusEnum;
import com.gxa.jd.enums.PayTypeEnum;
import com.gxa.jd.interceptor.UserThreadLocal;
import com.gxa.jd.mapper.PayLogMapper;
import com.gxa.jd.pojo.PayLog;
import com.gxa.jd.service.OrderService;
import com.gxa.jd.service.PayService;
import com.gxa.util.JsonUtils;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.PrivateKey;
import java.util.*;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class PayServiceImpl implements PayService {

    @Autowired
    private HttpServletRequest request;


    private OrderService orderService;

    @Autowired
    private PayLogMapper payLogMapper;


    @Autowired
    private RedisTemplate redisTemplate;


    @Transactional
    @Override
    public String createPayToken(String orderId, Integer type, Double price) throws CustomerException {

        //预支付订单信息不能重复生成
        if (payLogMapper.selectByPrimaryKey(orderId) != null) {
            log.error("已经创建过了预支付订单信息!,orderId:{}", orderId);
            throw new CustomerException(MessageCode.ERROR);
        }

        log.debug("创建预支付信息.........");
        PayLog payLog = new PayLog();
        //从订单获取总金额
        payLog.setTotalFee(price);
        payLog.setCreateTime(new Date());
        payLog.setType(type);
        payLog.setOutTradeNo(orderId);
        payLog.setUserId(UserThreadLocal.get().getId());
        payLog.setTradeState(PayStatusEnum.WAIT.getCode());
        payLogMapper.insert(payLog);

        log.debug("创建预支付信息为{}", JsonUtils.objectToJson(payLog));
        String payToken = UUID.randomUUID().toString();
        //存储订单id
        redisTemplate.boundValueOps(payToken).set(payLog.getOutTradeNo());
        //设置失效时间
        redisTemplate.boundValueOps(payToken).expire(PayConstant.PAY_ORDER_EXPIRE, TimeUnit.MINUTES);
        return payToken;
    }


    private void buildPcPay(HttpServletResponse response, String orderId, BigDecimal totalAmount, String orderName, String itemDescribe) throws CustomerException {
        try {
            log.info("支付订单号:{}", orderId);
            log.info("支付金额:{}", totalAmount);
            log.info("支付订名称:{}", orderName);

            // 获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id,
                    AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key,
                    AlipayConfig.sign_type);
            // 设置请求参数
            AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
            alipayRequest.setReturnUrl(AlipayConfig.return_url);
            alipayRequest.setNotifyUrl(AlipayConfig.notify_url);

            alipayRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\"," + "\"total_amount\":\""
                    + totalAmount.toString() + "\"," + "\"subject\":\"" + orderName + "\"," + "\"body\":\""
                    + itemDescribe + "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

            String result = alipayClient.pageExecute(alipayRequest).getBody();
            log.info("result:{}", result);
            response.setContentType("text/html;character=utf-8");
            response.getWriter().write(result);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("支付请求失败{}", e);
            throw new CustomerException(MessageCode.ERROR);
        }
    }


    private void buildMobilePay(HttpServletResponse response, String orderId, BigDecimal totalAmount, String orderName, String itemDescribe) throws CustomerException {
        try {
            log.info("支付订单号:{}", orderId);
            log.info("支付金额:{}", totalAmount);
            log.info("支付订名称:{}", orderName);
            //实例化客户端
            AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id,
                    AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key,
                    AlipayConfig.sign_type);

            //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称：alipay.trade.app.pay
            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
            //SDK已经封装掉了公共参数，这里只需要传入业务参数。以下方法为sdk的model入参方式
            // (model和biz_content同时存在的情况下取biz_content)。
            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
            model.setBody(itemDescribe);
            model.setSubject(orderName);
            model.setOutTradeNo(orderId);
            model.setTimeoutExpress("30m");
            model.setTotalAmount(totalAmount.toString());
            model.setProductCode("QUICK_MSECURITY_PAY");
            request.setBizModel(model);
            request.setNotifyUrl(AlipayConfig.notify_url);
            //这里和普通的接口调用不同，使用的是sdkExecute
            AlipayTradeAppPayResponse aliPayResponse = alipayClient.sdkExecute(request);
            //就是orderString 可以直接给客户端请求，无需再做处理。
            String result = aliPayResponse.getBody();
            log.info("返回客户端签名订单信息:{}，由客户端向支付宝发起支付请求!", request);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(result);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("支付请求失败{}", e);
            throw new CustomerException(MessageCode.ERROR);
        }
    }

    @Override
    public boolean verifyResult() {

        try {
            // 获取支付宝POST过来反馈信息
            Map<String, String> params = new HashMap<String, String>();
            Map<String, String[]> requestParams = request.getParameterMap();
            for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
                String name = (String) iter.next();
                String[] values = (String[]) requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
                }
                params.put(name, valueStr);
            }

            boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
                    AlipayConfig.charset, AlipayConfig.sign_type); // 调用SDK验证签名

            return signVerified;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    @Override
    public void pay(String payToken, Integer payType, HttpServletResponse response) throws CustomerException {
        if (StringUtils.isEmpty(payToken)) {
            log.error("支付token不能为空");
            throw new CustomerException(MessageCode.ERROR);
        }
        String payId = (String) redisTemplate.boundValueOps(payToken).get();
        if (StringUtils.isEmpty(payId)) {
            log.error("支付请求超时!");
            throw new CustomerException(MessageCode.ERROR);
        }
        PayLog payLog = payLogMapper.selectByPrimaryKey(payId);
        if (ObjectUtils.isEmpty(payLog)) {
            log.error("未找到支付信息!");
            throw new CustomerException(MessageCode.ERROR);
        }

        if (payType.equals(PayTypeEnum.PC.getCode())) {
            log.info("支付宝电脑网页支付...");
            buildPcPay(response, payLog.getOutTradeNo(), BigDecimal.valueOf(payLog.getTotalFee()).divide(BigDecimal.valueOf(100)), "baby易购订单", null);
        } else if (payType.equals(PayTypeEnum.MOBILE.getCode())) {
            log.info("支付宝手机app支付...");
            buildMobilePay(response, payLog.getOutTradeNo(), BigDecimal.valueOf(payLog.getTotalFee()).divide(BigDecimal.valueOf(100)), "baby易购订单", null);
        } else if (payType.equals(PayTypeEnum.MOBILE_WEB.getCode())) {
            log.info("支付宝手机网页支付...");
            //TODO...
        } else if (payType.equals(PayTypeEnum.WX_SM)) {
            log.info("微信扫码支付...");
            //TODO...
        } else if (payType.equals(PayTypeEnum.WX_APP)) {
            log.info("微信小程序支付...");
            buildSmallWxPay(response, payLog.getOutTradeNo(), BigDecimal.valueOf(payLog.getTotalFee()).divide(BigDecimal.valueOf(100)), "baby易购订单", null);
        }

    }

    public CloseableHttpClient createHttpClient() throws IOException {

        // 加载商户私钥（privateKey：私钥字符串）
        PrivateKey merchantPrivateKey = PemUtil
                .loadPrivateKey(new ByteArrayInputStream(WxPayConfig.merchant_private_key.getBytes("utf-8")));

        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3秘钥）
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WxPayConfig.mchid, new PrivateKeySigner(WxPayConfig.mchSerialNo, merchantPrivateKey)),WxPayConfig.apiV3Key.getBytes("utf-8"));

        // 初始化httpClient
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WxPayConfig.mchid, WxPayConfig.mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();
        return httpClient;
    }


    /**
     * 微信小程序支付
     * @param response
     * @param orderId
     * @param totalAmount
     * @param orderName
     * @param itemDescribe
     */
    public void buildSmallWxPay(HttpServletResponse response, String orderId, BigDecimal totalAmount, String orderName, String itemDescribe)  {

        try {

            //1.加载私钥和平台证书，创建CloseableHttpClient
            CloseableHttpClient httpClient = createHttpClient();

            //2.设置请求URL
            HttpPost httpPost = new HttpPost(WxPayConfig.gatewayUrl);

            //3.封装支付信息
            String reqdata = "{"
                    + "\"amount\": {"
                    + "\"total\": " + totalAmount
                    + "\"currency\": \"CNY\""
                    + "},"
                    + "\"mchid\": \"" + WxPayConfig.mchid + "\","
                    + "\"description\": \"酒店订单\","
                    + "\"notify_url\": \"" + WxPayConfig.notify_url + "\","
                    + "\"payer\": {"
                    + "\"openid\": \"" + UserThreadLocal.get().getOpenId() + "\"" + "},"
                    + "\"out_trade_no\": \"" + orderId + "\","
                    + "\"goods_tag\": \"微信支付测试\","
                    + "\"appid\": \"" + WxPayConfig.appId + "\"" + "}";
            StringEntity entity = new StringEntity(reqdata);
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");

            //4.发送支付信息给微信获取签名订单信息
            CloseableHttpResponse closeableHttpResponse = httpClient.execute(httpPost);

            int statusCode = closeableHttpResponse.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(closeableHttpResponse.getEntity()));

                //5.发送签名订单信息给微信小程序端
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write(EntityUtils.toString(closeableHttpResponse.getEntity()));

            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(closeableHttpResponse.getEntity()));
                throw new IOException("request failed");
            }

            closeableHttpResponse.close();
        }catch (Exception e){
            log.error(e.getMessage());
            log.error("创建微信小程序支付失败!");
        }
    }



    /**
     * 支付宝异步通知
     * @param params
     * @return
     * @throws CustomerException
     */
    @Override
    public synchronized String asynCallBack(Map<String, String> params) throws CustomerException {
        if (!verifyResult()) {
            //验签失败
            log.error("验签失败!");
            throw new CustomerException(MessageCode.ERROR);
        }
        String outTradeNo = params.get("out_trade_no");
        //判断是否有支付订单信息
        PayLog payLog = payLogMapper.selectByPrimaryKey(outTradeNo);
        if (payLog == null) {
            log.error("支付信息不存在!");
            throw new CustomerException(MessageCode.ERROR);
        }
        //判断订单状态是否已经支付
        if (payLog.getTradeState() == PayStatusEnum.SUCCESS.getCode()) {
            log.info("订单已经支付,orderId:{}", outTradeNo);
            return "success";
        }
        //校验支付订单金额是否一致
        String totalAmount = params.get("total_amount");

        BigDecimal totalFee = BigDecimal.valueOf(payLog.getTotalFee()).divide(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
        ;

        if (!totalFee.equals(new BigDecimal(totalAmount))) {
            log.error("支付金额不正确,orderId:{},支付金额:{},订单金额:{}", outTradeNo, totalAmount, totalFee);
            throw new CustomerException(MessageCode.ERROR);
        }


        // 支付宝交易号
        String tradeNo = params.get("trade_no");
        payLog.setPayTime(new Date());
        payLog.setTradeState(PayStatusEnum.SUCCESS.getCode());
        payLog.setTransactionId(tradeNo);

        payLogMapper.updateByPrimaryKey(payLog);

        //根据支付类型修改对应的订单状态
        //TODO

        return "success";
    }


}
